/*
 * ManagedWinapi - A collection of .NET components that wrap PInvoke calls to 
 * access native API by managed code. http://mwinapi.sourceforge.net/
 * Copyright (C) 2006 Michael Schierl
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING. if not, visit
 * http://www.gnu.org/licenses/lgpl.html or write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using WindowsAPI.Windows;

namespace WindowsAPI
{
    /// <summary>Specifies a component that creates a global keyboard hotkey.</summary>
    [DefaultEvent("HotkeyPressed")]
    public class Hotkey : Component
    {

        /// <summary>Occurs when the hotkey is pressed.</summary>
        public event EventHandler HotkeyPressed;

        private static readonly Object myStaticLock = new Object();
        private static int hotkeyCounter = 0xA000;

        private readonly int f_hotkeyIndex;
        private bool f_IsDisposed;
        private bool f_IsEnabled;
        private bool f_IsRegistered;
        private Keys f_KeyCode;
        private bool f_Ctrl;
        private bool f_Alt;
        private bool f_Shift;
        private bool f_Windows;
        private readonly IntPtr f_hWnd;
        private readonly EventDispatchingNativeWindow nativeWindow;

        /// <summary>Initializes a new instance of this class with the specified container.</summary>
        /// <param name="container">The container to add it to.</param>
        public Hotkey(IContainer container) : this() { container.Add(this); }

        /// <summary>Initializes a new instance of this class.</summary>
        public Hotkey()
        {
            nativeWindow = EventDispatchingNativeWindow.Instance;
            nativeWindow.EventHandler += nw_EventHandler;
            lock(myStaticLock)
                f_hotkeyIndex = ++hotkeyCounter;
            f_hWnd = nativeWindow.Handle;
        }

        /// <summary>
        /// Enables the hotkey. When the hotkey is enabled, pressing it causes a
        /// <c>HotkeyPressed</c> event instead of being handled by the active 
        /// application.
        /// </summary>
        public bool Enabled { get { return f_IsEnabled; } set { f_IsEnabled = value; UpdateHotkey(false); } }

        /// <summary>The key code of the hotkey.</summary>
        public Keys KeyCode { get { return f_KeyCode; } set { f_KeyCode = value; UpdateHotkey(true); } }

        /// <summary>Whether the shortcut includes the Control modifier.</summary>
        public bool Ctrl { get { return f_Ctrl; } set { f_Ctrl = value; UpdateHotkey(true); } }

        /// <summary>
        /// Whether this shortcut includes the Alt modifier.
        /// </summary>
        public bool Alt { get { return f_Alt; } set { f_Alt = value; UpdateHotkey(true); } }

        /// <summary>Whether this shortcut includes the shift modifier.</summary>
        public bool Shift { get { return f_Shift; } set { f_Shift = value; UpdateHotkey(true); } }

        /// <summary>
        /// Whether this shortcut includes the Windows key modifier. The windows key
        /// is an addition by Microsoft to the keyboard layout. It is located between
        /// Control and Alt and depicts a Windows flag.
        /// </summary>
        public bool WindowsKey { get { return f_Windows; } set { f_Windows = value; UpdateHotkey(true); } }

        void nw_EventHandler(ref Message m, ref bool handled)
        {
            if(handled) return;
            if(m.Msg != WM_HOTKEY || m.WParam.ToInt32() != f_hotkeyIndex) return;
            if(HotkeyPressed != null)
                HotkeyPressed(this, EventArgs.Empty);
            handled = true;
        }

        /// <summary>Releases all resources used by the System.ComponentModel.Component.</summary>
        /// <param name="disposing">Whether to dispose managed resources.</param>
        protected override void Dispose(bool disposing)
        {
            f_IsDisposed = true;
            UpdateHotkey(false);
            nativeWindow.EventHandler -= nw_EventHandler;
            base.Dispose(disposing);
        }

        private void UpdateHotkey(bool reregister)
        {
            var shouldBeRegistered = f_IsEnabled && !f_IsDisposed && !DesignMode;
            if(f_IsRegistered && (!shouldBeRegistered || reregister))
            {
                // unregister hotkey
                UnregisterHotKey(f_hWnd, f_hotkeyIndex);
                f_IsRegistered = false;
            }
            if(!f_IsRegistered && shouldBeRegistered)
            {
                // register hotkey
                var success = RegisterHotKey(f_hWnd, f_hotkeyIndex,
                    (f_Shift ? MOD_SHIFT : 0) + (f_Ctrl ? MOD_CONTROL : 0) +
                    (f_Alt ? MOD_ALT : 0) + (f_Windows ? MOD_WIN : 0), (int)f_KeyCode);
                if(!success) throw new HotkeyAlreadyInUseException();
                f_IsRegistered = true;
            }
        }

        #region PInvoke Declarations

        [DllImport(User32.FileName, SetLastError = true)]
        private static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vlc);
        [DllImport(User32.FileName, SetLastError = true)]
        private static extern bool UnregisterHotKey(IntPtr hWnd, int id);

        private const int MOD_ALT = 0x0001;
        private const int MOD_CONTROL = 0x0002;
        private const int MOD_SHIFT = 0x0004;
        private const int MOD_WIN = 0x0008;
        private const int WM_HOTKEY = 0x0312;

        #endregion

        public void SetKeys(Keyboard.KeyList Keys)
        {
            Ctrl = Keys.Contains(VirtualKey.Control)
                   || Keys.Contains(VirtualKey.Control_Left)
                   || Keys.Contains(VirtualKey.Control_Right);

            Alt = Keys.Contains(VirtualKey.Alt_Left)
                  || Keys.Contains(VirtualKey.Alt_Right);

            Shift = Keys.Contains(VirtualKey.Shift)
                   || Keys.Contains(VirtualKey.Shift_Left)
                   || Keys.Contains(VirtualKey.Shift_Right);

            WindowsKey = Keys.Contains(VirtualKey.LeftWindows)
                   || Keys.Contains(VirtualKey.RightWindows);

            Keys.RemoveAll(
                           vk =>
                           vk == VirtualKey.Control || vk == VirtualKey.Control_Left || vk == VirtualKey.Control_Right
                           || vk == VirtualKey.Alt_Left || vk == VirtualKey.Alt_Right
                           || vk == VirtualKey.Shift || vk == VirtualKey.Shift_Left || vk == VirtualKey.Shift_Right
                           || vk == VirtualKey.LeftWindows || vk == VirtualKey.RightWindows);

            KeyCode = (Keys)Keys[0];
        }
    }

    /// <summary>The exception is thrown when a hotkey should be registered that  has already been registered by another application.</summary>
    public class HotkeyAlreadyInUseException : Exception { }
}